Add support for external tests
authorBen Longbons <b.r.longbons@gmail.com>
Fri, 11 Jul 2014 00:08:19 +0000 (17:08 -0700)
committerBen Longbons <b.r.longbons@gmail.com>
Fri, 11 Jul 2014 20:41:26 +0000 (13:41 -0700)
src/cargo/core/manifest.rs
src/cargo/util/toml.rs
tests/test_cargo_test.rs

index 6d130aa6f59d8b4ae980fd5abd0f5817cccb71dd..fb43b1c4354642fdd96ed578afc37751458dc1ef 100644 (file)
@@ -353,6 +353,16 @@ impl Target {
         }
     }
 
+    pub fn test_target(name: &str, src_path: &Path, profile: &Profile) -> Target {
+        Target {
+            kind: BinTarget,
+            name: name.to_string(),
+            src_path: src_path.clone(),
+            profile: profile.clone(),
+            metadata: None
+        }
+    }
+
     pub fn get_name<'a>(&'a self) -> &'a str {
         self.name.as_slice()
     }
index b8c62cc07a37b9071faa0e54fc13c65c61d5195a..5a0f35e7fb5aed775419f7737afcf1413e0a4b76 100644 (file)
@@ -16,6 +16,7 @@ pub struct Layout {
     lib: Option<Path>,
     bins: Vec<Path>,
     examples: Vec<Path>,
+    tests: Vec<Path>,
 }
 
 impl Layout {
@@ -33,6 +34,7 @@ pub fn project_layout(root: &Path) -> Layout {
     let mut lib = None;
     let mut bins = vec!();
     let mut examples = vec!();
+    let mut tests = vec!();
 
     if root.join("src/lib.rs").exists() {
         lib = Some(root.join("src/lib.rs"));
@@ -54,10 +56,22 @@ pub fn project_layout(root: &Path) -> Layout {
         .map(|mut i| i.collect())
         .map(|found| examples.push_all_move(found));
 
+    // support two styles of tests: src/test.rs or tests/*.rs
+    let _ = fs::readdir(&root.join("tests"))
+        .map(|v| v.move_iter())
+        .map(|i| i.filter(|f| f.extension_str() == Some("rs")))
+        .map(|mut i| i.collect())
+        .map(|found| tests.push_all_move(found));
+
+    if root.join("src/test.rs").exists() {
+        tests.push(root.join("src/test.rs"));
+    }
+
     Layout {
         lib: lib,
         bins: bins,
         examples: examples,
+        tests: tests,
     }
 }
 
@@ -139,6 +153,7 @@ pub fn parse(toml: &str, file: &str) -> CargoResult<toml::Table> {
 type TomlLibTarget = TomlTarget;
 type TomlBinTarget = TomlTarget;
 type TomlExampleTarget = TomlTarget;
+type TomlTestTarget = TomlTarget;
 
 /*
  * TODO: Make all struct fields private
@@ -168,6 +183,7 @@ pub struct TomlManifest {
     lib: Option<Vec<TomlLibTarget>>,
     bin: Option<Vec<TomlBinTarget>>,
     example: Option<Vec<TomlExampleTarget>>,
+    test: Option<Vec<TomlTestTarget>>,
     dependencies: Option<HashMap<String, TomlDependency>>,
     dev_dependencies: Option<HashMap<String, TomlDependency>>
 }
@@ -254,6 +270,22 @@ fn inferred_example_targets(layout: &Layout) -> Option<Vec<TomlTarget>> {
     }).collect())
 }
 
+fn inferred_test_targets(layout: &Layout) -> Option<Vec<TomlTarget>> {
+    Some(layout.tests.iter().filter_map(|ex| {
+        let name = ex.filestem_str().map(|f| f.to_string());
+
+        name.map(|name| {
+            TomlTarget {
+                name: name,
+                crate_type: None,
+                path: Some(ex.display().to_string()),
+                test: None,
+                plugin: None,
+            }
+        })
+    }).collect())
+}
+
 impl TomlManifest {
     pub fn to_manifest(&self, source_id: &SourceId, layout: &Layout)
         -> CargoResult<(Manifest, Vec<Path>)>
@@ -319,10 +351,19 @@ impl TomlManifest {
             }).collect())
         };
 
+        let tests = if self.test.is_none() || self.test.get_ref().is_empty() {
+            inferred_test_targets(layout)
+        } else {
+            Some(self.test.get_ref().iter().map(|t| {
+                t.clone()
+            }).collect())
+        };
+
         // Get targets
         let targets = normalize(lib.as_ref().map(|l| l.as_slice()),
                                 bins.as_ref().map(|b| b.as_slice()),
                                 examples.as_ref().map(|e| e.as_slice()),
+                                tests.as_ref().map(|e| e.as_slice()),
                                 &metadata);
 
         if targets.is_empty() {
@@ -423,10 +464,12 @@ struct TomlTarget {
 fn normalize(lib: Option<&[TomlLibTarget]>,
              bin: Option<&[TomlBinTarget]>,
              example: Option<&[TomlExampleTarget]>,
+             test: Option<&[TomlTestTarget]>,
              metadata: &Metadata)
              -> Vec<Target>
 {
-    log!(4, "normalizing toml targets; lib={}; bin={}; example={}", lib, bin, example);
+    log!(4, "normalizing toml targets; lib={}; bin={}; example={}; test={}",
+         lib, bin, example, test);
 
     enum TestDep { Needed, NotNeeded }
 
@@ -495,6 +538,20 @@ fn normalize(lib: Option<&[TomlLibTarget]>,
         }
     }
 
+    fn test_targets(dst: &mut Vec<Target>, tests: &[TomlTestTarget],
+                   default: |&TomlTestTarget| -> String) {
+        for test in tests.iter() {
+            let path = test.path.clone().unwrap_or_else(|| default(test));
+
+            let profile = &Profile::default_test();
+            {
+                dst.push(Target::test_target(test.name.as_slice(),
+                                            &Path::new(path.as_slice()),
+                                            profile));
+            }
+        }
+    }
+
     let mut ret = Vec::new();
 
     match (lib, bin) {
@@ -522,5 +579,18 @@ fn normalize(lib: Option<&[TomlLibTarget]>,
         None => ()
     }
 
+    match test {
+        Some(ref tests) => {
+            test_targets(&mut ret, tests.as_slice(),
+                        |test| {
+                            if test.name.as_slice() == "test" {
+                                "src/test.rs".to_string()
+                            } else {
+                                format!("tests/{}.rs", test.name)
+                            }});
+        },
+        None => ()
+    }
+
     ret
 }
index bdc429a4ec31c8872577a237b1f57e0b29cb4068..ed2136ab5594b689f2f4959cd46db61256fd9ac1 100644 (file)
@@ -132,3 +132,93 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured\n\n\
                        compiling = COMPILING,
                        dir = p.root().display()).as_slice()));
 })
+
+test!(external_test_explicit {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+
+            [[test]]
+            name = "test"
+            path = "src/test.rs"
+        "#)
+        .file("src/lib.rs", r#"
+            pub fn get_hello() -> &'static str { "Hello" }
+
+            #[test]
+            fn internal_test() {}
+        "#)
+        .file("src/test.rs", r#"
+            extern crate foo;
+
+            #[test]
+            fn external_test() { assert_eq!(foo::get_hello(), "Hello") }
+        "#);
+
+    let output = p.cargo_process("cargo-test")
+                  .exec_with_output().assert();
+    let out = str::from_utf8(output.output.as_slice()).assert();
+
+    let internal = "\
+running 1 test
+test internal_test ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured";
+    let external = "\
+running 1 test
+test external_test ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured";
+
+    let head = format!("{compiling} foo v0.0.1 (file:{dir})",
+                       compiling = COMPILING, dir = p.root().display());
+
+    assert!(out == format!("{}\n\n{}\n\n\n{}\n\n", head, internal, external).as_slice() ||
+            out == format!("{}\n\n{}\n\n\n{}\n\n", head, external, internal).as_slice());
+})
+
+test!(external_test_implicit {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+        "#)
+        .file("src/lib.rs", r#"
+            pub fn get_hello() -> &'static str { "Hello" }
+
+            #[test]
+            fn internal_test() {}
+        "#)
+        .file("src/test.rs", r#"
+            extern crate foo;
+
+            #[test]
+            fn external_test() { assert_eq!(foo::get_hello(), "Hello") }
+        "#);
+
+    let output = p.cargo_process("cargo-test")
+                  .exec_with_output().assert();
+    let out = str::from_utf8(output.output.as_slice()).assert();
+
+    let internal = "\
+running 1 test
+test internal_test ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured";
+    let external = "\
+running 1 test
+test external_test ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured";
+
+    let head = format!("{compiling} foo v0.0.1 (file:{dir})",
+                       compiling = COMPILING, dir = p.root().display());
+
+    assert!(out == format!("{}\n\n{}\n\n\n{}\n\n", head, internal, external).as_slice() ||
+            out == format!("{}\n\n{}\n\n\n{}\n\n", head, external, internal).as_slice());
+})